package wavedigest;

import java.util.List;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.jdo.PersistenceManager;
import javax.mail.Message;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

import wavedigest.data.EmailRegistration;
import wavedigest.data.PMF;
import wavedigest.data.RegisteredWave;
import wavedigest.data.WaveChange;

import com.google.appengine.repackaged.com.google.common.util.Base64;
import com.google.wave.api.AbstractRobotServlet;
import com.google.wave.api.Blip;
import com.google.wave.api.Event;
import com.google.wave.api.Gadget;
import com.google.wave.api.RobotMessageBundle;
import com.google.wave.api.TextView;
import com.google.wave.api.Wavelet;

@SuppressWarnings("serial")
public class WaveDigestRobot extends AbstractRobotServlet
{
	private static final String SUFFIX_KEY_SCHEDULE = "-schedule";
	private static final String SUFFIX_KEY_EMAIL = "-email";
	Logger log = Logger.getLogger("WaveDigestRobot");

	@Override
	public void processEvents(RobotMessageBundle bundle)
	{
		Wavelet wavelet = bundle.getWavelet();

		if (bundle.wasSelfAdded())
			onSelfAdded(wavelet, wavelet.getRootBlip());

		if (bundle.wasSelfRemoved())
			onSelfRemoved(wavelet);

		for (Event e : bundle.getEvents())
		{
			switch (e.getType())
			{
				case BLIP_SUBMITTED:
					onBlipSubmitted(wavelet, e);
					break;

				case WAVELET_PARTICIPANTS_CHANGED:
					onWaveletParticipantsChanged(wavelet, e);
					break;

				default:
					log.info("unknown event type: " + e.getType());
					break;
			}
		}
	}

	@SuppressWarnings("unchecked")
	private void onSelfRemoved(Wavelet wavelet)
	{
		PersistenceManager pm = PMF.get().getPersistenceManager();
		try
		{
			String query = "select from " + RegisteredWave.class.getName() + " where waveID == '" + wavelet.getWaveId() + "'";
			List<RegisteredWave> waveList = (List<RegisteredWave>) pm.newQuery(query).execute();
			if (waveList.isEmpty())
			{
				log.severe("Wave was not registered: " + wavelet.getWaveId());
				return;
			}

			pm.deletePersistentAll(waveList);
		}
		finally
		{
			pm.close();
		}
	}

	@SuppressWarnings("unchecked")
	private void onSelfAdded(Wavelet wavelet, Blip rootBlip)
	{
		log.info("wave digest was added to wave" + wavelet.getWaveId());

		PersistenceManager pm = PMF.get().getPersistenceManager();
		try
		{
			String query = "select from " + RegisteredWave.class.getName() + " where waveID == '" + wavelet.getWaveId() + "'";
			List<RegisteredWave> waveList = (List<RegisteredWave>) pm.newQuery(query).execute();
			if (waveList.isEmpty())
				pm.makePersistent(new RegisteredWave(wavelet.getWaveId()));
		}
		finally
		{
			pm.close();
		}

		Blip blip = rootBlip.createChild();
		blip.getDocument().appendElement(new Gadget(getGadgetURL(wavelet)));
	}

	private String getGadgetURL(Wavelet wavelet)
	{
		String url = "http://wavedigest.appspot.com/registerGadget.xml?wid=" + Base64.encode(wavelet.getWaveId().getBytes());
		log.info("getGadgetURL returning: " + url);
		return url;
	}

	@SuppressWarnings("unchecked")
	private void onBlipSubmitted(Wavelet wavelet, Event e)
	{
		log.info("blip submitted for wave " + wavelet.getWaveId());

		// see if this wave is an instruction
		Blip blip = e.getBlip();
		if (blip.getDocument().getText().startsWith("!force"))
		{
			// TODO
		}
		else if (blip.getDocument().getText().startsWith("!form"))
		{
			onSelfAdded(wavelet, blip);
			return;
		}

		// if this blip was for registration, then dont send any emails.
		if (performRegistrations(wavelet, e, blip))
			return;

		PersistenceManager pm = PMF.get().getPersistenceManager();
		try
		{
			// what changed?
			WaveChange change = new WaveChange(e);
			
			// send updates for "immediate" registrations
			String query = "select from " + RegisteredWave.class.getName() + " where waveID == '" + wavelet.getWaveId() + "'";
			List<RegisteredWave> waveList = (List<RegisteredWave>) pm.newQuery(query).execute();
			if (waveList.isEmpty())
			{
				log.severe("Wave was not registered: " + wavelet.getWaveId());
				return;
			}

			RegisteredWave regWave = waveList.get(0);
			String waveID = regWave.getWaveID();
			for (EmailRegistration registration : regWave.getRegistrations())
			{
				if (registration.getScheduleType() == EmailRegistration.SCHEDULE_TYPE_EVERY_CHANGE)
					sendUpdateEmail(waveID, registration, change);
			}
		
			// persist this change for future updates (cron updates)
			regWave.getChanges().add(change);
			pm.makePersistent(regWave);
			
		}
		finally
		{
			pm.close();
		}
		
		// TODO: update RSS feed
		
		// TODO: call webhooks
	}

	private void sendUpdateEmail(String waveID, EmailRegistration registration, WaveChange change)
	{
		Properties props = new Properties();
		Session session = Session.getDefaultInstance(props, null);

		try
		{
			log.info("Sending update email: waveid=" + waveID + ", email=" + registration.getEmailAddress());
			Message msg = new MimeMessage(session);
			msg.setFrom(new InternetAddress("adam.schepis@gmail.com", "WaveDigest Updates"));
			msg.addRecipient(Message.RecipientType.TO, new InternetAddress(registration.getEmailAddress()));
			msg.setSubject("Update to wave" + waveID);
			
			StringBuilder body = new StringBuilder();
			body.append("Modified By: " + change.getModifiedBy() + "\n\n");
			body.append(change.getChangeDescription());
			
			msg.setText(body.toString());
			Transport.send(msg);
		}
		catch (Exception e1)
		{
			log.log(Level.SEVERE, "Error sending mail: ", e1);
		}
	}

	@SuppressWarnings("unchecked")
	private boolean performRegistrations(Wavelet wavelet, Event e, Blip blip)
	{
		// check to see if this wave contains registration
		Gadget gadget = blip.getDocument().getGadgetView().getGadget(getGadgetURL(wavelet));
		if (null == gadget)
			return false;

		String emailKey = new String(e.getModifiedBy() + SUFFIX_KEY_EMAIL);
		String scheduleKey = new String(e.getModifiedBy() + SUFFIX_KEY_SCHEDULE);

		String email = gadget.getProperty(emailKey);
		int schedule = -1;
		try
		{
			schedule = Integer.parseInt(gadget.getProperty(scheduleKey));
		}
		catch (NumberFormatException nfe)
		{
			log.warning("number format exception parsing schedule value.");
			schedule = -1;
		}

		if (null != email && email.length() > 0 && -1 != schedule)
		{
			PersistenceManager pm = PMF.get().getPersistenceManager();
			try
			{
				String query = "select from " + RegisteredWave.class.getName() + " where waveID == '" + wavelet.getWaveId() + "'";
				List<RegisteredWave> waveList = (List<RegisteredWave>) pm.newQuery(query).execute();
				if (waveList.isEmpty())
				{
					log.severe("Wave was not registered: " + wavelet.getWaveId());
					return false;
				}

				RegisteredWave regWave = waveList.get(0);
				if (EmailRegistration.SCHEDULE_TYPE_UNSUBSCRIBE == schedule)
				{
					// TODO: very inefficient.. use a query.
					log.info("deleting registration for " + email);
					EmailRegistration deleteReg = null;
					for (EmailRegistration reg : regWave.getRegistrations())
					{
						if (reg.getEmailAddress().equalsIgnoreCase(email))
						{
							deleteReg = reg;
							break;
						}
					}
					
					if (null != deleteReg)
					{
						pm.deletePersistent(deleteReg);
					}
				}
				else
				{
					log.info("adding registration for " + email + " (schedule type " + schedule + ")");
					regWave.getRegistrations().add(new EmailRegistration(email, schedule));
	
					pm.makePersistent(regWave);
				}
			}
			finally
			{
				pm.close();
			}

			return true;
		}

		return false;
	}

	private void onWaveletParticipantsChanged(Wavelet wavelet, Event e)
	{
		log.info("participants changed for wave " + wavelet.getWaveId());

		StringBuilder greeting = new StringBuilder();
		boolean bFirst = true;
		for (String participant : e.getAddedParticipants())
		{
			if (participant.equals("wavedigest@appspot.com"))
				continue;

			if (!bFirst)
				greeting.append(", ");

			greeting.append(participant);
			bFirst = false;
		}

		if (greeting.toString().length() > 0)
		{
			Blip blip = wavelet.appendBlip();
			blip.getDocument().appendMarkup(
					greeting.toString() + " -- if you want to get notified of updates to this wave simply register at the top of the wave.");
		}
	}
}
